#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Rules for Series

@time: 2020/09/18 21:18:11
@author: hhhhhliu
"""

import json
import warnings
import re
from typing import Dict, Tuple, Union, List


def maximum_lengths(length_list1: List[int], length_list2: List[int]) -> List[int]:
    """
    将两个length_list合并

    Args:
        length_list1 (List[int]): length_list1
        length_list2 (List[int]): length_list2

    Returns:
        List[int]: maximum length_list
    """
    new_lengths = [0] * max(len(length_list1), len(length_list2))
    for i, l_ in enumerate(length_list1):
        new_lengths[i] = l_
    for i, l_ in enumerate(length_list2):
        new_lengths[i] = max(new_lengths[i], l_)
    return new_lengths


def get_outer_bracket(
    string: str,
    bracket: Tuple[str, str] = ("(", ")")
) -> List[Tuple[int, str, int]]:
    """
    获取最外层括号起始index及其内容content

    Args:
        string (str): str
        bracket (Tuple[str, str], optional): ("(", ")") | ("[", "]"). Defaults to ("(", ")").

    Raises:
        ValueError: unpaired bracket

    Returns:
        List[Tuple[int, str, int]]: List[Tuple[start, content, end]]
    """
    result = []
    b_count = 0
    start = 0
    for i, s in enumerate(string):
        if s == bracket[0]:
            b_count += 1
            if b_count == 1:
                start = i
        elif s == bracket[1]:
            b_count -= 1
            if b_count < 0:
                raise ValueError("unpaired bracket")
            elif b_count == 0:
                end = i
                content = string[start + 1:i]
                result.append((start, content, end))
    return result


def replace_bracket_content(
    string: str,
    bracket: Tuple[str, str] = ("(", ")")
) -> Tuple[str, List[int]]:
    """
    raplace content in bracket (including bracket) into r_string

    Args:
        string (str): string that has bracket
        bracket (Tuple[str, str], optional): ("(", ")") | ("[", "]"). Defaults to ("(", ")").

    Raises:
        ValueError: only support () []

    Returns:
        Tuple[str, List[int]]: Tuple[replaced string, List[len(content) + 2]]
    """
    outer_brackets = get_outer_bracket(string, bracket)
    string_nobracket = string
    content_len_sum = 0
    content_len_list = []
    for start, content, end in outer_brackets:
        if bracket[0] == "(":
            r_string = "roundbracket%d" % len(content_len_list)
        elif bracket[0] == "[":
            r_string = "squarebracket%d" % len(content_len_list)
        else:
            raise ValueError("unrecognized bracket:{}".format(bracket))
        string_nobracket = (
            string_nobracket[:start - content_len_sum] +
            r_string +
            string_nobracket[end + 1 - content_len_sum:]
        )
        content_len_sum += len(content) + 2 - len(r_string)
        content_len_list.append(len(content) + 2)
    return string_nobracket, content_len_list


def compute_right_move(
    string_nobracket: str,
    start: int,
    content_len_list: List[int],
    bracket: Tuple[str, str] = ("(", ")")
) -> int:
    """
    replace r_string into (XX) need right move

    Args:
        string_nobracket (str): string that has r_string
        start (int): start index
        content_len_list ([type]): List[len(content) + 2]
        bracket (Tuple[str, str], optional): ("(", ")") | ("[", "]"). Defaults to ("(", ")").

    Raises:
        ValueError: only support () []

    Returns:
        int: right move step
    """
    right_move = 0
    rule1_left = string_nobracket[:start]

    if bracket[0] == "(":
        r_string = "roundbracket%d"
    elif bracket[0] == "[":
        r_string = "squarebracket%d"
    else:
        raise ValueError("unrecognized bracket:{}".format(bracket))

    for i, content_len in enumerate(content_len_list):
        if r_string % i in rule1_left:
            right_move += content_len - len(r_string % i)
        else:
            break
    return right_move


class Item:
    """
    Value Unit

    Example Single Series:
        1. "l"
        2. "l0"
        3. "l1"
        4. "l2"
        or:
        1. "t"
        2. "t-1"
        3. "t-2"
    Example Multiple Series:
        1. "s[0][l]"
        2. "s[1][l0]"
        3. "s[0][l1]"
        4. "s[0][l2]"
        or:
        1. "s[0][t]"
        2. "s[1][t-1]"
        3. "s[1][t-2]"
    """
    INDEX = "i"
    CONSTANT = "c"

    def __init__(self, item: str) -> None:
        item = item.replace(" ", "").replace("\t", "")
        self.item_str = item
        if "s[" in item and "]" in item:  # multiple series
            self.is_multiple = True
            self.series_num, self.type, self.value = \
                self._parse_item_with_square_brackets(item)
        else:  # single series
            self.is_multiple = False
            self.type, self.value = self._parse_item(item)
            self.series_num = 0

        self.length = self.compute_length()

    @classmethod
    def _parse_item(cls, item: str) -> Tuple[str, Union[int, float]]:
        """get type, index/value in item"""
        if "l" in item or "t" in item:
            type_ = cls.INDEX
            if item == "l":
                value = 0
            elif item == "t":
                value = -1
            else:
                value = int(item[1:])
                if value < 0:
                    value -= 1
        else:
            type_, value = cls.CONSTANT, float(item)
        return type_, value

    @classmethod
    def _parse_item_with_square_brackets(
        cls,
        item: str
    ) -> Tuple[int, str, Union[int, float]]:
        """get series_num, type, index/value in item"""
        if "s[" in item and "]" in item:
            series_num = int(item.split("s[")[1].split("]")[0])
            type_value = item.split("[")[-1].split("]")[0]
        else:
            series_num = 0
            type_value = item
        type_, value = cls._parse_item(type_value)
        return series_num, type_, value

    def format_string(self) -> str:
        """get format string"""
        return json.dumps(self.format(), indent=4)

    def format(self) -> Dict[str, str]:
        """get format dict"""
        return {"item": self.item_str}

    def compute_length(self) -> List[int]:
        if self.type == self.CONSTANT:
            length = 0
        else:
            if self.value >= 0:
                length = self.value + 1
            else:
                length = - self.value
        lengths = [0] * (self.series_num + 1)
        lengths[self.series_num] = length
        return lengths

    def __call__(
        self,
        value_list: Union[List[Union[int, float]], List[List[Union[int, float]]]]
    ) -> Union[int, float]:
        return self.get_value(value_list)

    def get_value(
        self,
        value_list: Union[List[Union[int, float]], List[List[Union[int, float]]]]
    ) -> Union[int, float]:
        if self.is_multiple:
            if self.type == self.CONSTANT:
                return self.value
            else:
                return value_list[self.series_num][self.value]
        else:
            if self.type == self.CONSTANT:
                return self.value
            else:
                return value_list[self.value]


class Items:
    """
    Computing Unit

    Example Single Series:
        1. "l+5"
        2. "l0-5"
        3. "l1*10"
        4. "l2/l1"
        or:
        1. "t*5"
        2. "(t-1)/10"
        3. "(t-2)-(t)-1"
    Example Multiple Series:
        1. "s[0][l]+5"
        2. "s[1][l0]-5"
        3. "s[0][l1]*10"
        4. "s[0][l2]/s[1][l1]"
        or:
        1. "s[0][t]*5"
        2. "s[1][t-1]/10"
        3. "s[1][t-2]-s[0][t-1]"
    """
    opt_dict = {
        "+": lambda x, y: x + y,
        "-": lambda x, y: x - y,
        "*": lambda x, y: x * y,
        "/": lambda x, y: x / y
    }

    def __init__(self, item: str = "0-0") -> None:
        item = item.replace(" ", "").replace("\t", "")
        if item[0] == "(" and item[-1] == ")":  # remove redundant bracket
            item = item[1:-1]
        self.item_str = item
        if self.item_str[0] == '-':
            self.item_str = '0' + self.item_str

        self.item = None
        self.right = None
        self.left = None

        item_nobracket, content_len_list = (
            replace_bracket_content(self.item_str))
        item_nobracket_s, content_len_list_s = (
            replace_bracket_content(item_nobracket, bracket=("[", "]")))
        self.type, opt_start, opt_end = self._split_opt(
            item_nobracket_s)
        right_move = compute_right_move(
            item_nobracket_s, opt_start, content_len_list_s, bracket=("[", "]"))
        opt_start += right_move
        opt_end += right_move
        right_move = compute_right_move(
            item_nobracket, opt_start, content_len_list)
        opt_start += right_move
        opt_end += right_move
        if self.type == "leaf":
            self.opt_func = None
            self.item = Item(self.item_str)
        else:
            self.opt_func = self.opt_dict[self.type]
            self.left = Items(self.item_str[:opt_start])
            self.right = Items(self.item_str[opt_end:])

        if self.type == "leaf":
            self.length = self.item.length.copy()
        else:
            self.length = maximum_lengths(self.left.length, self.right.length)

    def __repr__(self) -> str:
        return self.item_str

    def __len__(self) -> int:
        return max(self.length)

    def format_string(self) -> str:
        """get format string"""
        return json.dumps(self.format(), indent=4)

    def format(self) -> Dict[str, str]:
        """get format dict"""
        if self.type == "leaf":
            return self.item.format()
        else:
            return {
                "opt": self.type,
                "left": self.left.format(),
                "right": self.right.format()
            }

    @classmethod
    def _split_opt(cls, item_nobracket: str) -> Tuple[str, int, int]:
        for opt in cls.opt_dict:
            opt_start = item_nobracket.find(opt)
            if opt_start >= 0:
                if opt_start > 0 and item_nobracket[opt_start-1] == "t":
                    warnings.warn(
                        "单序列时，t-N存在歧义，默认作'倒数第N个值'用；请使用(t)-N来表示'最后一个值减去N'")
                    continue
                opt_end = opt_start + len(opt)
                opt_str = opt
                break
        else:
            opt_str, opt_start, opt_end = "leaf", 0, 0
        return opt_str, opt_start, opt_end

    def __call__(
        self,
        value_list: Union[List[Union[int, float]], List[List[Union[int, float]]]]
    ) -> Union[int, float]:
        return self.get_value(value_list)

    def get_value(
        self,
        value_list: Union[List[Union[int, float]], List[List[Union[int, float]]]]
    ) -> Union[int, float]:
        if self.type == "leaf":
            return self.item(value_list)
        else:
            return self.opt_func(
                self.left(value_list),
                self.right(value_list)
            )


# rule
class SeriesRule:
    """
    Logical Computing Unit.
    Rule for Series.
    【Attention】:
        1. l或t均可, 不同点在于当只有字母没有数字时, t代表最后1位list[-1], l代表第1位list[0]
        2. 若字母后的数字为非负数n时, 代表list[n]; 为负数-n时, 代表list[-n-1].
        3. s[0]代表第1个list, s[1]代表第2个list

    Example Single Series:
        1. "l>5"
        2. "l0>5"
        3. "l1<=10"
        4. "l2<l1"
        or:
        1. "t>5"
        2. "t-1<=10"
        3. "t-2<t-1"
    Example Multiple Series:
        1. "s[0][l]>5"
        2. "s[1][l0]>5"
        3. "s[0][l1]<=10"
        4. "s[0][l2]<s[1][l1]"
        or:
        1. "s[0][t]>5"
        2. "s[1][t-1]<=10"
        3. "s[1][t-2]<s[0][t-1]"
    """
    opt_dict = {
        "==": lambda x, y: x == y,
        "!=": lambda x, y: x != y,
        ">=": lambda x, y: x >= y,
        "<=": lambda x, y: x <= y,
        "=": lambda x, y: x == y,
        ">": lambda x, y: x > y,
        "<": lambda x, y: x < y
    }

    def __init__(self, rule: str = "1>0") -> None:
        rule = rule.replace(" ", "").replace("\t", "")
        self.rule_str = rule

        item1, self.opt_str, item2 = self._parse_rule(self.rule_str)  # left_str, opt_str, right_str
        self.opt_func = self.opt_dict[self.opt_str]
        self.item1 = Items(item1)  # type, index/value
        self.item2 = Items(item2)  # type, index/value

        self.length = maximum_lengths(self.item1.length, self.item2.length)

    def __repr__(self) -> str:
        return self.rule_str

    def __len__(self) -> int:
        return max(self.length)

    def format_string(self) -> str:
        """get format string"""
        return json.dumps(self.format(), indent=4)

    def format(self) -> Dict[str, str]:
        """get format dict"""
        return {
            "rule": {
                "opt": self.opt_str,
                "left": self.item1.format(),
                "right": self.item2.format()
            }
        }

    @classmethod
    def _parse_rule(cls, rule: str) -> Tuple[str, str, str]:
        """split rule string into left_str, opt_str, right_str"""
        for opt in cls.opt_dict:
            opt_start = rule.find(opt)
            if opt_start >= 0:
                opt_end = opt_start + len(opt)
                break
        else:
            raise ValueError("unrecognized opt in %s" % rule)
        item1, opt_str, item2 = rule[:opt_start], opt, rule[opt_end:]
        opt_uniqueness = True
        for opt in cls.opt_dict:
            if item1.find(opt) >= 0 or item2.find(opt) >= 0:
                opt_uniqueness = False
                break
        if not opt_uniqueness:
            raise ValueError("%s has more than one logical operators")
        return item1, opt_str, item2

    def __call__(
        self,
        value_list: Union[List[Union[int, float]], List[List[Union[int, float]]]]
    ) -> bool:
        return self.check(value_list)

    def check(
        self,
        value_list: Union[List[Union[int, float]], List[List[Union[int, float]]]]
    ) -> bool:
        v1 = self.item1(value_list)
        v2 = self.item2(value_list)
        result = self.opt_func(v1, v2)
        return result


# %%
# rules
class SeriesRules:
    """
    Combinational Logical Computing Unit.
    Rules for Series.
    【Attention】:
        1. l或t均可, 不同点在于当只有字母没有数字时, t代表最后1位list[-1], l代表第1位list[0]
        2. 若字母后的数字为非负数n时, 代表list[n]; 为负数-n时, 代表list[-n-1].
        3. s[0]代表第1个list，s[1]代表第2个list

    Example Single Series:
        1. "l>5 and l0>5 & (l1<=10 or l2<l1) | t>5 & not t-1<=10 or ~ t-2<t-1"
    Example Multiple Series:
        1. "s[0][l]>5 and s[1][l0]>5 & (s[0][l1]<=10 or s[1][l2]<s[0][l1])"
        " | s[0][t]>5 & not s[1][t-1]<=10 or ~ s[0][t-2]<s[0][t-1]"
    """
    logical_or_list = ["or", "|"]
    logical_and_list = ["and", "&"]
    logical_not_list = ["not", "~"]

    def __init__(self, rule: str = "1>0") -> None:
        rule = rule.replace(" ", "").replace("\t", "")
        if rule[0] == "(" and rule[-1] == ")":  # remove redundant bracket
            rule = rule[1:-1]
        self.rule_str = rule

        self.rule = None
        self.right = None
        self.left = None

        rule_nobracket, content_len_list = (
            replace_bracket_content(self.rule_str))
        self.type, logical_start, logical_end = self._split_logical(
            rule_nobracket)
        right_move = compute_right_move(
            rule_nobracket, logical_start, content_len_list)
        logical_start += right_move
        logical_end += right_move
        if self.type == "leaf":
            self.rule = SeriesRule(self.rule_str)
        else:
            self.right = SeriesRules(self.rule_str[logical_end:])
            if self.type != "not":
                self.left = SeriesRules(self.rule_str[:logical_start])

        if self.type == "leaf":
            self.length = self.rule.length.copy()
        else:
            if self.type == "not":
                self.length = self.right.length.copy()
            else:
                self.length = maximum_lengths(self.left.length, self.right.length)

    def __repr__(self) -> str:
        return self.rule_str

    def __len__(self) -> int:
        return max(self.length)

    def format_string(self) -> str:
        """get format string"""
        return json.dumps(self.format(), indent=4)

    def format(self) -> Dict[str, str]:
        """get format dict"""
        if self.type == "leaf":
            return self.rule.format()
        elif self.type == "not":
            return {
                "logical": self.type,
                "right": self.right.format()
            }
        else:
            return {
                "logical": self.type,
                "left": self.left.format(),
                "right": self.right.format()
            }

    @classmethod
    def _split_logical(cls, rule_nobracket: str) -> Tuple[str, int, int]:
        """split rule string into logical, logical_start, logical_end"""
        for logical_or in cls.logical_or_list:
            logical_start = rule_nobracket.find(logical_or)
            if logical_start >= 0:
                logical_end = logical_start + len(logical_or)
                logical = "or"
                break
        else:
            for logical_and in cls.logical_and_list:
                logical_start = rule_nobracket.find(logical_and)
                if logical_start >= 0:
                    logical_end = logical_start + len(logical_and)
                    logical = "and"
                    break
            else:
                for logical_not in cls.logical_not_list:
                    logical_start = rule_nobracket.find(logical_not)
                    if logical_start >= 0:
                        logical_end = logical_start + len(logical_not)
                        logical = "not"
                        break
                else:
                    logical, logical_start, logical_end = "leaf", 0, 0
        return logical, logical_start, logical_end

    def __call__(
        self,
        value_list: Union[List[Union[int, float]], List[List[Union[int, float]]]]
    ) -> bool:
        return self.check(value_list)

    def check(
        self,
        value_list: Union[List[Union[int, float]], List[List[Union[int, float]]]]
    ) -> bool:
        if self.type == "leaf":
            return self.rule.check(value_list)
        elif self.type == "or":
            if self.left.check(value_list):
                return True
            if self.right.check(value_list):
                return True
            return False
        elif self.type == "and":
            if not self.left.check(value_list):
                return False
            if not self.right.check(value_list):
                return False
            return True
        elif self.type == "not":
            return not self.right.check(value_list)


class Rule:
    """
    Uniform Rule
    【Attention】:
        1. Single Series must have s[*]
        2. lN <-> s[N]
        3. t <-> s[-1], t-N <-> s[-N-1]

    Example Single Series:
        1. "s[0]>5 and s[0]>5 & (s[1]<=10 or s[2]<s[1]) | s[-1]>5 & not s[-2]<=10 or ~ s[-3]<s[-2]"
    Example Multiple Series:
        1. "s[0][0]>5 and s[1][0]>5 & (s[0][1]<=10 or s[1][2]<s[0][1])"
        " | s[0][-1]>5 & not s[1][-2]<=10 or ~ s[0][-3]<s[0][2]"
    """
    PATTERN = re.compile(r"[^\s\(\)\[\](not)(or)(and)\d\.\+\-\*/&|~><=!]")
    PATTERN_MULTIPLE_SERIESNUM_LENGTH = re.compile(
        r"s\[(?P<num1>\-*\d)\]\[(?P<num2>\-*\d)\]")
    PATTERN_SINGLE_LENGTH = re.compile(r"s\[(?P<num1>\-*\d)\]")

    def __init__(self, rule: str = "1>0") -> None:
        # self.func = literal_eval("lambda s: %s" % rule)
        self.rule_str = rule.replace("&", " and ").replace("|", " or ").replace("~", " not ")
        self.is_multiple = "s[" in self.rule_str and "][" in self.rule_str
        safe, result = self.check_safe()
        if safe:
            self.func = eval("lambda s: %s" % self.rule_str)
        else:
            raise ValueError(result)

        self.length = self.compute_length()

    def check_safe(self) -> Tuple[bool, str]:
        tmp_str = self.rule_str.replace("s", "")
        result = Rule.PATTERN.search(tmp_str)
        return (not bool(result)), str(result)

    def compute_length(self) -> List[int]:
        if self.is_multiple:
            seriesnum_length_list = self.PATTERN_MULTIPLE_SERIESNUM_LENGTH.findall(
                self.rule_str
            )
            seriesnum_list = []
            length_list = []
            for series_num, length in seriesnum_length_list:
                series_num = int(series_num)
                length = int(length)
                length = (-length) if length < 0 else (length + 1)
                seriesnum_list.append(series_num)
                length_list.append(length)
            max_series_num = max(seriesnum_list)
            lengths = [0] * (max_series_num + 1)
            for series_num, length in zip(seriesnum_list, length_list):
                lengths[series_num] = max(lengths[series_num], length)
            return lengths
        else:
            length_list = self.PATTERN_SINGLE_LENGTH.findall(self.rule_str)
            max_len = 0
            for length in length_list:
                length = int(length)
                length = (-length) if length < 0 else (length + 1)
                max_len = max(max_len, length)
            return [max_len]

    def __repr__(self) -> str:
        return self.rule_str

    def __len__(self) -> int:
        return max(self.length)

    def __call__(
        self,
        value_list: Union[List[Union[int, float]], List[List[Union[int, float]]]]
    ) -> Union[bool, int, float]:
        return self.check(value_list)

    def check(
        self,
        value_list: Union[List[Union[int, float]], List[List[Union[int, float]]]]
    ) -> Union[bool, int, float]:
        result = False
        try:
            result = self.func(value_list)
        except IndexError:
            pass
        return result


if __name__ == "__main__":
    pass
    # compute
    print("----- Item -----")
    item_list = [
        Item("l0"),
        Item("t-1"),
        Item("t")
    ]
    value_list = [3, 2, 1]
    for item in item_list:
        print(item(value_list))

    print("----- Rule -----")
    item_list = [
        Rule("s[0]"),
        Rule("s[-2]"),
        Rule("s[-1]")
    ]
    value_list = [3, 2, 1]
    for item in item_list:
        print(item(value_list))

    print("----- Multiple Item -----")
    item_list = [
        Item("s[0][l0]"),
        Item("s[1][t-1]"),
        Item("s[0][t]")
    ]
    value_list = [[3, 2, 1], [6, 5, 4]]
    for item in item_list:
        print(item(value_list))

    print("----- Multiple Rule -----")
    item_list = [
        Rule("s[0][0]"),
        Rule("s[1][-2]"),
        Rule("s[0][-1]")
    ]
    value_list = [[3, 2, 1], [6, 5, 4]]
    for item in item_list:
        print(item(value_list))

    print("----- Items -----")
    value_list = [3, 2, 1]
    items = Items("(l0 + t-1) / t")
    print(items(value_list))
    items = Items("(l0 + (t)-1) / t")
    print(items(value_list))

    value_list = [[3, 2, 1], [6, 5, 4]]
    items = Items("(s[0][l0] + s[1][t-1]) / s[1][t]")
    print(items(value_list))

    print("----- Rule -----")
    value_list = [3, 2, 1]
    items = Rule("(s[0] + s[-2]) / s[-1]")
    print(items(value_list))
    items = Rule("(s[0] + (s[-1])-1) / s[-1]")
    print(items(value_list))

    value_list = [[3, 2, 1], [6, 5, 4]]
    items = Rule("(s[0][0] + s[1][-2]) / s[1][-1]")
    print(items(value_list))

    # single series
    print("----- single list SeriesRule -----")
    rule_list = [
        SeriesRule("t > t-1"),
        SeriesRule("l < l1"),
        SeriesRule("l0 < l1"),
        SeriesRule("l0 > 10"),
        SeriesRule("l1 <= 5")
    ]
    rules = SeriesRules(
        (
            "~t > t-1 and "
            "not l < l1 and "
            "(~l0 < l1 & l0 > 10) and "
            "l1 <= 5"
        )
    )

    value_list = [11, 5]
    print([rule(value_list) for rule in rule_list])
    print(rules(value_list))
    print([len(rule) for rule in rule_list])
    print(len(rules))
    print([str(rule) for rule in rule_list])
    print(str(rules))
    for rule in rule_list:
        print(rule.format_string())
    print(rules.format_string())
    print()

    print("----- single list Rule -----")
    rule_list = [
        Rule("s[-1] > s[-2]"),
        Rule("s[0] < s[1]"),
        Rule("s[0] < s[1]"),
        Rule("s[0] > 10"),
        Rule("s[1] <= 5")
    ]
    rules = Rule(
        (
            "~s[-1] > s[-2] and "
            "not s[0] < s[1] and "
            "(~s[0] < s[1] & s[0] > 10) and "
            "s[1] <= 5"
        )
    )

    value_list = [11, 5]
    print([rule(value_list) for rule in rule_list])
    print(rules(value_list))
    print([len(rule) for rule in rule_list])
    print(len(rules))
    print([str(rule) for rule in rule_list])
    print(str(rules))
    print()

    # multiple series
    print("----- multiple list SeriesRule -----")
    rule_list = [
        SeriesRule("s[0][t] > s[1][t-1]"),
        SeriesRule("s[1][l] < s[0][l1]"),
        SeriesRule("s[1][l0] >= s[1][l1]"),
        SeriesRule("s[0][l0] > 10"),
        SeriesRule("s[1][l1] <= 10")
    ]
    rules = SeriesRules(
        (
            "~s[0][t] > s[1][t-1] and "
            "not s[1][l] < s[0][l1] and "
            "(~s[1][l0] >= s[1][l1] & s[0][l0] > 10) and "
            "s[1][l1] <= 10"
        )
    )

    value_lists = [[11, 5], [6, 10]]
    print([rule(value_lists) for rule in rule_list])
    print(rules(value_lists))
    print([len(rule) for rule in rule_list])
    print(len(rules))
    print([str(rule) for rule in rule_list])
    print(str(rules))
    for rule in rule_list:
        print(rule.format_string())
    print(rules.format_string())
    print()

    print("----- multiple list Rule -----")
    rule_list = [
        Rule("s[0][-1] > s[1][-2]"),
        Rule("s[1][0] < s[0][1]"),
        Rule("s[1][0] >= s[1][1]"),
        Rule("s[0][0] > 10"),
        Rule("s[1][1] <= 10")
    ]
    rules = Rule(
        (
            "~s[0][-1] > s[1][-2] and "
            "not s[1][0] < s[0][1] and "
            "(~s[1][0] >= s[1][1] & s[0][0] > 10) and "
            "s[1][1] <= 10"
        )
    )

    value_lists = [[11, 5], [6, 10]]
    print([rule(value_lists) for rule in rule_list])
    print(rules(value_lists))
    print([len(rule) for rule in rule_list])
    print(len(rules))
    print([str(rule) for rule in rule_list])
    print(str(rules))
    print()
